package com.gusto.engine.repository.dao.impl;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.gusto.engine.beans.exceptions.ResCreationException;
import com.gusto.engine.beans.model.Res;
import com.gusto.engine.beans.model.impl.SemanticModelRes;
import com.gusto.engine.repository.dao.RepositoryDAO;
import com.gusto.engine.repository.exception.RepositoryException;
import com.gusto.engine.repository.params.Param;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.ModelMaker;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.util.FileManager;

/**
 * <p>A semantic repository relying on the Jena API.</p>
 * 
 * @author amokrane.belloui@gmail.com
 */
public class SemanticRepositoryDAO implements RepositoryDAO {
	
	private Logger log = Logger.getLogger(getClass());
	
	private ModelMaker modelMaker = null;
	private String modelName = null;
	private boolean OWL_INFERENCES = false;
	
	private boolean modelNeedRefresh = true;
	private Model model;
	
	/**
	 * Sets the Jena ModelMaker
	 * @param modelMaker
	 * @throws RepositoryException 
	 */
	public void setModelMaker(ModelMaker modelMaker) throws RepositoryException {
		this.modelMaker = modelMaker;
		modelNeedRefresh = true;
		log.debug("Setting the modelMaker " + modelMaker);
	}
	
	/**
	 * Sets the name for the model that will be created and then used
	 * @param modelName
	 * @throws RepositoryException 
	 */
	public void setModelName(String modelName) throws RepositoryException {
		this.modelName = modelName;
		modelNeedRefresh = true;
		log.debug("Setting the model name " + modelName);
	}
	
	public void setOWL_INFERENCES(boolean owl_inferences) {
		OWL_INFERENCES = owl_inferences;
		modelNeedRefresh = true;
		log.debug("Setting the OWL " + OWL_INFERENCES);
	}
	
	/**
	 * Checks if the model needs to be created or recreated
	 */
	private void checkModelRefresh() throws RepositoryException {
		if (modelNeedRefresh) {
			log.debug("Model needs to be refreshed");
			try {
				log.info("Refreshing the model ...");
				// The database model
				//modelMaker.createModel(modelName, true);
				Model dbModel = modelMaker.openModel(modelName, true);
				// Decorating the model with OWL inferences or others
				if (OWL_INFERENCES) {
					OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM_RDFS_INF);
					m.add(dbModel);
					model = m;
					log.info("OWL model " + modelName + " created");
				}
				else {
					model = dbModel;
					log.info("Database model " + modelName + " created");
				}
				modelNeedRefresh = false;
			}
			catch (Exception ex) {
				model = null;
				log.error("Error in refreshing Model", ex);
				throw new RepositoryException("Cannot create the Jena model", ex);
			}
		}
	}
	
	/**
	 * 1 Param is required [ResourceURI]
	 */
	public Res getResource(Object... params) throws ResCreationException, RepositoryException {
		checkModelRefresh();
		
		log.debug("Loading Resource " + params);
		
		Res res = SemanticModelRes.createResource(model.getResource(params[0].toString()));
		return res;
	}

	/**
	 * Query the semantic repository [QueryString] [QueryParams] [SolutionNameToConsider]
	 */
	public List<Res> query(String queryString, Param[] queryParams, Object... params) throws RepositoryException, ResCreationException {
		checkModelRefresh();
		
		log.debug("Query : \n" + queryString);
		
		Query query = QueryFactory.create(queryString);
		QueryExecution qExec = QueryExecutionFactory.create(query, this.model);
		
		List<Res> resources = new ArrayList<Res>();
		
		ResultSet rset = qExec.execSelect();
		while (rset.hasNext()) {
			QuerySolution sol = (QuerySolution)rset.next();
			Resource res = (Resource)sol.get((String)params[0]);
			resources.add(SemanticModelRes.createResource(res));
		}
		return resources;
	}
	
	/**
	 * Create a repository
	 * @param newModelName
	 * @throws FileNotFoundException
	 * @throws RepositoryException
	 */
	public void createRepository(String newModelName) throws FileNotFoundException, RepositoryException {
		log.info("Creating a model for " + newModelName + "...");
		modelMaker.createModel(newModelName, true);
	}
	
	/**
	 * Save resources to an RDF file
	 * @param resources
	 * @param file
	 * @throws FileNotFoundException
	 * @throws RepositoryException
	 */
	public void saveToRDF(List<Res> resources, File file) throws FileNotFoundException, RepositoryException {
		checkModelRefresh();
		
		Model model = ModelFactory.createDefaultModel();
		for (Res res : resources) {
			SemanticModelRes semRes = (SemanticModelRes)res;
			model.add(semRes.getResource().listProperties());
		}
		
		FileOutputStream os = new FileOutputStream(file);
		BufferedOutputStream bos = new BufferedOutputStream(os);
		model.write(bos);
	}
	
	/**
	 * Load data from an RDF file into the repository
	 * @param fileLocation
	 * @throws RepositoryException
	 */
	public void loadFromRDF(String fileLocation) throws RepositoryException {
		checkModelRefresh();
		long start, stop;
		
		log.debug("Loading data into '" + model + "' ...");
		start = System.currentTimeMillis();
		InputStream in = FileManager.get().open(fileLocation);
		model.read(in, "");
		stop = System.currentTimeMillis();
		log.info("Data loaded into '" + modelName + "' in " + (stop - start) + " milliseconds");
	}
	
	/**
	 * Empty the repository
	 * @throws RepositoryException
	 */
	public void emptyRepository() throws RepositoryException {
		checkModelRefresh();
		
		model.removeAll();
		
		log.info("Data removed from '" + modelName + "'");
	}
	
}
